﻿using System.Collections.Generic;
using System.Diagnostics;
using Antlr.Runtime;
using Microsoft.VisualStudio.Package;

namespace DLanguagePackage
{
    class LexerWrapper
    {
        private enum LexerType
        {
            None, LineLexer, ContinuationLexer
        }

        /// <summary>
        /// describes the meaning of the state variable that is returned from 
        /// ScanTokenAndProvideInfoAboutIt. If state > NestingLevel1, then it means nesting 
        /// level = 1 + (state - NestingLevel1)
        /// </summary>
        private enum State
        {
            LineLexer,
            MultiLineComment,
            AlternateWysiwygString,
            DoubleQuotedString,
            HexadecimalStringEven,  //encountered an even # of hex digits so far
            HexadecimalStringOdd,   //encountered an odd # of hex digits so far
            WysiwygString,
            NestingLevel1,  //must be last
        }
        
        LineLexer lineLex = new LineLexer();
        ContinuationLexer continuationLexer = new ContinuationLexer();
        ANTLRStringStream stream;
        Lexer curLexer;
        LexerType curLexerType;

        /// <summary>
        /// translates LineLexer token types to a token type Visual Studio 
        /// understands
        /// </summary>
        static Dictionary<int, TokenType> lineLexerToVSTokenType = new Dictionary<int, TokenType>() 
        {
            //comments
            { LineLexer.MultiLineComment, TokenType.Comment }, 
            { LineLexer.StartNestingBlockComment, TokenType.Comment }, 
            { LineLexer.LineComment, TokenType.LineComment },
            //Strings
            { LineLexer.AlternateWysiwygString, TokenType.String },
            { LineLexer.StartAlternateWysiwygString, TokenType.String },
            { LineLexer.DoubleQuotedString, TokenType.String },
            { LineLexer.StartDoubleQuotedString, TokenType.String },
            { LineLexer.HexadecimalString, TokenType.String },
            { LineLexer.StartHexadecimalString, TokenType.String },
            { LineLexer.WysiwygString, TokenType.String },
            { LineLexer.StartWysiwygString, TokenType.String },
            //literals
            { LineLexer.CharacterLiteral, TokenType.Literal},
            { LineLexer.IntegerLiteral, TokenType.Literal},
            { LineLexer.FloatLiteral, TokenType.Literal},
            { LineLexer.Keyword, TokenType.Keyword},
            { LineLexer.Identifier, TokenType.Identifier},
            { LineLexer.Operator, TokenType.Operator},
            { LineLexer.Punctuation, TokenType.Delimiter},//TODO refine this. there is punctuation, and there is delimiter
            { LineLexer.WhiteSpace, TokenType.WhiteSpace},
            { LineLexer.SpecialToken, TokenType.Text},
            { LineLexer.SpecialTokenSequence, TokenType.Text},
        };

        /// <summary>
        /// translates ContinuationLexer token types to a token type Visual Studio 
        /// understands
        /// </summary>
        static Dictionary<int, TokenType> continuationLexerToVSTokenType = new Dictionary<int, TokenType>() 
        {
            //comments
            { ContinuationLexer.ContinueNestingBlockComment, TokenType.Comment }, 
            { ContinuationLexer.ContinueMultiLineComment, TokenType.Comment }, 
            //Strings
            { ContinuationLexer.ContinueAlternateWysiwygString, TokenType.String },
            { ContinuationLexer.ContinueDoubleQuotedString, TokenType.String },
            { ContinuationLexer.ContinueHexadecimalString, TokenType.String },
            { ContinuationLexer.ContinueWysiwygString, TokenType.String },
        };

        /// <summary>
        /// translates LineLexer token colors to a token color Visual Studio 
        /// understands
        /// </summary>
        static Dictionary<int, TokenColor> lineLexerToVSTokenColor = new Dictionary<int, TokenColor>() 
        {
            //comments
            { LineLexer.MultiLineComment, TokenColor.Comment }, 
            { LineLexer.StartNestingBlockComment, TokenColor.Comment }, 
            { LineLexer.LineComment, TokenColor.Comment },
            //Strings
            { LineLexer.AlternateWysiwygString, TokenColor.String },
            { LineLexer.StartAlternateWysiwygString, TokenColor.String },
            { LineLexer.DoubleQuotedString, TokenColor.String },
            { LineLexer.StartDoubleQuotedString, TokenColor.String },
            { LineLexer.HexadecimalString, TokenColor.String },
            { LineLexer.StartHexadecimalString, TokenColor.String },
            { LineLexer.WysiwygString, TokenColor.String },
            { LineLexer.StartWysiwygString, TokenColor.String },
            //literals
            { LineLexer.CharacterLiteral, TokenColor.String },
            { LineLexer.IntegerLiteral, TokenColor.Number },
            { LineLexer.FloatLiteral, TokenColor.Number },
            { LineLexer.Keyword, TokenColor.Keyword },
            { LineLexer.Identifier, TokenColor.Identifier },
            { LineLexer.Operator, TokenColor.Text },
            { LineLexer.Punctuation, TokenColor.Text },//TODO refine this. there is punctuation, and there is delimiter
            { LineLexer.WhiteSpace, TokenColor.Text },
            { LineLexer.SpecialToken, TokenColor.Text },
            { LineLexer.SpecialTokenSequence, TokenColor.Text },
        };

        /// <summary>
        /// translates ContinuationLexer token colors to a token color Visual Studio 
        /// understands
        /// </summary>
        static Dictionary<int, TokenColor> continuationLexerToVSTokenColor = new Dictionary<int, TokenColor>() 
        {
            { ContinuationLexer.ContinueMultiLineComment, TokenColor.Comment }, 
            { ContinuationLexer.ContinueNestingBlockComment, TokenColor.Comment }, 
        };

        public LexerWrapper()
        {
            curLexer = lineLex;
            curLexerType = LexerType.LineLexer;
        }

        public bool ScanTokenAndProvideInfoAboutIt(TokenInfo tokenInfo, ref int state)
        {
            IToken antlrToken;
            SelectActiveLexer((State)state);
            tokenInfo.StartIndex = curLexer.CharIndex;
            try
            {
                //TODO should we add a singular nesting block comment to LineLexer to avoid changing active lexer?
                antlrToken = curLexer.NextToken();
            }
            catch (RecognitionException)
            {
                //TODO this really what i should do? or return unknown and recover the stream?
                tokenInfo.Type = TokenType.Unknown;
                tokenInfo.Color = TokenColor.Text;
                return false;
            }

            //a token composed just of '\n' is something I added, so return false
            if (antlrToken == Tokens.EndOfFile || antlrToken.Text == "\n")
            {
                return false;
            }

            tokenInfo.EndIndex = curLexer.CharIndex - 1;
            tokenInfo.Type = GetType(antlrToken);
            tokenInfo.Color = GetColor(antlrToken);

            //trim the trailing \n that I added
            if (antlrToken.Text[antlrToken.Text.Length - 1] == '\n')
            {
                --tokenInfo.EndIndex;
            }

            state = (int)SelectActiveLexer(antlrToken);

            //TODO am I doing the right thing?
            /* The token ID represents a particular language token and is specific to the scanner or parser used. This could be used, 
             * for example, to quickly find a specific token in a parse tree if more information about that token is needed than 
             * can be represented in the TokenInfo class.*/
            //tokenInfo.Token= ?

            //TODO do something with state
            return true;
        }

        /// <summary>
        /// if we don't already have an active lexer, choose one using state
        /// </summary>
        /// <param name="state"></param>
        private void SelectActiveLexer(State state)
        {
            if (curLexerType != LexerType.None)
            {
                return;
            }
            switch (state)
            {
                case State.LineLexer:
                    curLexerType = LexerType.LineLexer;
                    lineLex.CharStream = stream;
                    break;
                case State.MultiLineComment:
                    curLexerType = LexerType.ContinuationLexer;
                    continuationLexer.CharStream = stream;
                    continuationLexer.insideMultiLineComment = true;
                    break;
                case State.AlternateWysiwygString:
                    curLexerType = LexerType.ContinuationLexer;
                    continuationLexer.CharStream = stream;
                    continuationLexer.insideAlternateWysiwygString = true;
                    break;
                case State.DoubleQuotedString:
                    curLexerType = LexerType.ContinuationLexer;
                    continuationLexer.CharStream = stream;
                    continuationLexer.insideDoubleQuotedString = true;
                    break;
                case State.HexadecimalStringEven:
                    curLexerType = LexerType.ContinuationLexer;
                    continuationLexer.CharStream = stream;
                    continuationLexer.insideHexadecimalString = true;
                    continuationLexer.numHexDigits = 0;
                    break;
                case State.HexadecimalStringOdd:
                    curLexerType = LexerType.ContinuationLexer;
                    continuationLexer.CharStream = stream;
                    continuationLexer.insideHexadecimalString = true;
                    continuationLexer.numHexDigits = 1;
                    break;
                case State.WysiwygString:
                    curLexerType = LexerType.ContinuationLexer;
                    continuationLexer.CharStream = stream;
                    continuationLexer.insideWysiwygString = true;
                    break;
                case State.NestingLevel1:
                default:
                    curLexerType = LexerType.ContinuationLexer;
                    continuationLexer.CharStream = stream;
                    continuationLexer.nestingLevel = 1 + state - State.NestingLevel1;
                    break;
            }
            switch (curLexerType)
            {
                case LexerType.LineLexer:
                    curLexer = lineLex;
                    break;
                case LexerType.ContinuationLexer:
                    curLexer = continuationLexer;
                    break;
                case LexerType.None:
                default:
                    throw new System.ApplicationException("unexpected value of curLexerType");
            }
        }


        /// <summary>
        /// based on current lexer and current token, possibly change the current lexer and 
        /// decide what state to return
        /// The part of this function that switches to a new lexer is only for performance, we 
        /// could use the other SelectActiveLexer() and be correct. The part of this function 
        /// that returns the new state is needed.
        /// </summary>
        /// <param name="antlrToken">token we just got from the input stream</param>
        private State SelectActiveLexer(IToken currenttoken)
        {
            LexerType nextLexer = curLexerType;
            switch (curLexerType)
            {
                case LexerType.LineLexer:
                    switch (currenttoken.Type)
                    {
                        case LineLexer.MultiLineComment:
                            if (lineLex.insideMultiLineComment)
                            {
                                ChangeCurrentLexer(LexerType.ContinuationLexer, currenttoken.Type);
                                return State.MultiLineComment;
                            }
                            return State.LineLexer;
                        case LineLexer.StartAlternateWysiwygString:
                            ChangeCurrentLexer(LexerType.ContinuationLexer, currenttoken.Type);
                            return State.AlternateWysiwygString;
                        case LineLexer.StartDoubleQuotedString:
                            ChangeCurrentLexer(LexerType.ContinuationLexer, currenttoken.Type);
                            return State.DoubleQuotedString;
                        case LineLexer.StartHexadecimalString:
                            ChangeCurrentLexer(LexerType.ContinuationLexer, currenttoken.Type);
                            return lineLex.numHexDigits % 2 == 0 ? State.HexadecimalStringEven : State.HexadecimalStringOdd;
                        case LineLexer.StartWysiwygString:
                            ChangeCurrentLexer(LexerType.ContinuationLexer, currenttoken.Type);
                            return State.WysiwygString;
                        case LineLexer.StartNestingBlockComment:
                            ChangeCurrentLexer(LexerType.ContinuationLexer, currenttoken.Type);
                            return State.NestingLevel1;
                        default:
                            //no change
                            return State.LineLexer;
                    }
                case LexerType.ContinuationLexer:
                    switch (currenttoken.Type)
                    {
                        case ContinuationLexer.ContinueAlternateWysiwygString:
                            if (!continuationLexer.insideAlternateWysiwygString)
                            {
                                ChangeCurrentLexer(LexerType.LineLexer, currenttoken.Type);
                                return State.LineLexer;
                            }
                            return State.AlternateWysiwygString;
                        case ContinuationLexer.ContinueDoubleQuotedString:
                            if (!continuationLexer.insideDoubleQuotedString)
                            {
                                ChangeCurrentLexer(LexerType.LineLexer, currenttoken.Type);
                                return State.LineLexer;
                            }
                            return State.DoubleQuotedString;
                        case ContinuationLexer.ContinueHexadecimalString:
                            if (!continuationLexer.insideHexadecimalString)
                            {
                                ChangeCurrentLexer(LexerType.LineLexer, currenttoken.Type);
                                return State.LineLexer;
                            }
                            return continuationLexer.numHexDigits % 2 == 0 ? State.HexadecimalStringEven : State.HexadecimalStringOdd;
                        case ContinuationLexer.ContinueWysiwygString:
                            if (!continuationLexer.insideWysiwygString)
                            {
                                ChangeCurrentLexer(LexerType.LineLexer, currenttoken.Type);
                                return State.LineLexer;
                            }
                            return State.WysiwygString;
                        case ContinuationLexer.ContinueMultiLineComment:
                            if (!continuationLexer.insideMultiLineComment)
                            {
                                ChangeCurrentLexer(LexerType.LineLexer, currenttoken.Type);
                                return State.LineLexer;
                            }
                            return State.MultiLineComment;
                        case ContinuationLexer.ContinueNestingBlockComment:
                            if (continuationLexer.nestingLevel < 1)
                            {
                                ChangeCurrentLexer(LexerType.LineLexer, currenttoken.Type);
                                return State.LineLexer;
                            }
                            return State.NestingLevel1 + (continuationLexer.nestingLevel - 1);
                        default:
                            throw new System.ApplicationException("unexpected value of currenttoken.Type");
                    }
                default:
                    throw new System.ApplicationException("unexpected value of curLexerType");
            }
        }

        private void ChangeCurrentLexer(LexerType nextLexerType, int tokenType)
        {
            Lexer nextLexer;
            switch (nextLexerType)
            {
                case LexerType.LineLexer:
                    lineLex.insideMultiLineComment = false;
                    lineLex.numHexDigits = 0;
                    nextLexer = lineLex;
                    break;
                case LexerType.ContinuationLexer:
                    Debug.Assert(curLexerType == LexerType.LineLexer);
                    switch (tokenType)
                    {
                        case LineLexer.StartAlternateWysiwygString:
                            continuationLexer.insideAlternateWysiwygString = true;
                            break;
                        case LineLexer.StartDoubleQuotedString:
                            continuationLexer.insideDoubleQuotedString = true;
                            break;
                        case LineLexer.StartHexadecimalString:
                            continuationLexer.insideHexadecimalString = true;
                            continuationLexer.numHexDigits = lineLex.numHexDigits;
                            break;
                        case LineLexer.StartWysiwygString:
                            continuationLexer.insideWysiwygString = true;
                            break;
                        case LineLexer.MultiLineComment:
                            continuationLexer.insideMultiLineComment = true;
                            break;
                        case LineLexer.StartNestingBlockComment:
                            continuationLexer.nestingLevel = 1;
                            break;
                        default:
                            break;
                    }
                    nextLexer = continuationLexer;
                    break;
                default:
                    throw new System.ApplicationException("unexpected value of curLexerType");
            }
            nextLexer.CharStream = curLexer.CharStream;
            curLexerType = nextLexerType;
            curLexer = nextLexer;
        }

        public void SetSource(string source, int offset)
        {
            Debug.Assert(!source.Contains("\n"));
            Debug.Assert(!source.Contains("\r"));
            /* Append a newline to assist my lexer. It's hard to work with EOF. Patterns like 
             * this don't work too well: ('/' (~'+' | EOF))
             * Visual Studio has stripped all the newlines out of the input, essentially making all
             * newlines into EOF. So add the newline back in.
             */
            stream = new ANTLRStringStream(source + '\n');
            stream.CharPositionInLine = offset;
            curLexerType = LexerType.None;
            curLexer = null;
        }

        /// <summary>
        /// TODO should be directed by current lexer type
        /// translate from my lexer token types to VS's token types
        /// </summary>
        /// <param name="tok">the antlr token whose type we're translating</param>
        /// <returns>the visual studio token type</returns>
        TokenType GetType(IToken tok)
        {
            TokenType retval;
            switch (curLexerType)
            {
                case LexerType.LineLexer:
                    if (!lineLexerToVSTokenType.TryGetValue(tok.Type, out retval))
                    {
                        retval = TokenType.Unknown;
                    }
                    break;
                case LexerType.ContinuationLexer:
                    if (!continuationLexerToVSTokenType.TryGetValue(tok.Type, out retval))
                    {
                        retval = TokenType.Unknown;
                    }
                    break;
                case LexerType.None:
                default:
                    throw new System.ApplicationException("unexpected value of curLexerType");
            }
            return retval;
        }

        /// <summary>
        /// TODO should be directed by current lexer type
        /// translates antlr tokens to VS token colors
        /// </summary>
        /// <param name="tok">the token to translate</param>
        /// <returns>a visual studio token color</returns>
        TokenColor GetColor(IToken tok)
        {
            TokenColor retval;
            switch (curLexerType)
            {
                case LexerType.LineLexer:
                    if (!lineLexerToVSTokenColor.TryGetValue(tok.Type, out retval))
                    {
                        retval = TokenColor.Text;
                    }
                    break;
                case LexerType.ContinuationLexer:
                    if (!continuationLexerToVSTokenColor.TryGetValue(tok.Type, out retval))
                    {
                        retval = TokenColor.Text;
                    }
                    break;
                case LexerType.None:
                default:
                    throw new System.ApplicationException("unexpected value of curLexerType");
            }
            return retval;
        }
    }
}
